Padroneggia la Resource Timing API per diagnosticare e ottimizzare le prestazioni del frontend. Impara a misurare il tempo di caricamento di ogni risorsa, dalla ricerca DNS al download dei contenuti.
Sbloccare le Prestazioni del Frontend: Un'Analisi Approfondita della Resource Timing API
Nel mondo dello sviluppo web, la velocità non è solo una caratteristica; è un requisito fondamentale per un'esperienza utente positiva. Un sito web a caricamento lento può portare a una maggiore frequenza di rimbalzo, un minor coinvolgimento degli utenti e, in definitiva, un impatto negativo sugli obiettivi di business. Sebbene strumenti come Lighthouse e WebPageTest forniscano preziose diagnosi di alto livello, spesso rappresentano un singolo test sintetico. Per comprendere e ottimizzare veramente le prestazioni per un pubblico globale, dobbiamo misurare l'esperienza degli utenti reali, sui loro dispositivi, sulle loro reti. È qui che entra in gioco il Real User Monitoring (RUM), e uno dei suoi strumenti più potenti è la Resource Timing API.
Questa guida completa vi condurrà in un'analisi approfondita della Resource Timing API. Esploreremo cos'è, come usarla e come trasformare i suoi dati granulari in informazioni strategiche che possono migliorare drasticamente le prestazioni di caricamento della vostra applicazione. Che siate ingegneri frontend esperti o che abbiate appena iniziato il vostro percorso di ottimizzazione delle prestazioni, questo articolo vi fornirà le conoscenze per analizzare e comprendere le prestazioni di rete di ogni singolo asset sulla vostra pagina.
Cos'è la Resource Timing API?
La Resource Timing API è un'API JavaScript basata su browser che fornisce dati dettagliati sui tempi di rete per ogni risorsa scaricata da una pagina web. Pensatela come una lente d'ingrandimento per l'attività di rete della vostra pagina. Per ogni immagine, script, foglio di stile, font e chiamata API (tramite `fetch` o `XMLHttpRequest`), questa API cattura un timestamp ad alta risoluzione per ogni fase della richiesta di rete.
Fa parte di una suite più ampia di API di Performance, che lavorano insieme per fornire una visione olistica delle prestazioni della vostra applicazione. Mentre la Navigation Timing API si concentra sul ciclo di vita del documento principale, la Resource Timing API si focalizza su tutte le risorse dipendenti richieste dal documento principale.
Perché è così importante?
- Granularità: Va oltre una singola metrica di "tempo di caricamento della pagina". Potete vedere precisamente quanto tempo hanno impiegato la ricerca DNS, la connessione TCP e il download del contenuto per uno specifico script di terze parti o per un'immagine hero critica.
- Dati Utente Reali: A differenza degli strumenti basati su test di laboratorio, questa API viene eseguita nei browser dei vostri utenti. Ciò consente di raccogliere dati sulle prestazioni da una vasta gamma di condizioni di rete, dispositivi e posizioni geografiche, offrendovi un quadro fedele della vostra esperienza utente globale.
- Informazioni Strategiche: Analizzando questi dati, potete individuare specifici colli di bottiglia. Uno script di analisi di terze parti è lento a connettersi? La vostra CDN ha prestazioni inferiori in una certa regione? Le vostre immagini sono troppo grandi? La Resource Timing API fornisce le prove necessarie per rispondere a queste domande con sicurezza.
L'Anatomia del Caricamento di una Risorsa: Decostruire la Timeline
Il cuore della Resource Timing API è l'oggetto `PerformanceResourceTiming`. Per ogni risorsa caricata, il browser crea uno di questi oggetti, che contiene una ricchezza di informazioni su tempi e dimensioni. Per comprendere questi oggetti, è utile visualizzare il processo di caricamento come un grafico a cascata (waterfall chart), dove ogni passo segue il precedente.
Analizziamo le proprietà chiave di un oggetto `PerformanceResourceTiming`. Tutti i valori temporali sono timestamp ad alta risoluzione misurati in millisecondi dall'inizio della navigazione della pagina (`performance.timeOrigin`).
startTime -> fetchStart -> domainLookupStart -> domainLookupEnd -> connectStart -> connectEnd -> requestStart -> responseStart -> responseEnd
Proprietà Chiave Relative ai Tempi
name: L'URL della risorsa. Questo è il vostro identificatore primario.entryType: Una stringa che indica il tipo di entry di performance. Per i nostri scopi, sarà sempre "resource".initiatorType: Questo è incredibilmente utile per il debug. Vi dice come è stata richiesta la risorsa. I valori comuni includono 'img', 'link' (per CSS), 'script', 'css' (per risorse caricate dall'interno di CSS come `@import`), 'fetch' e 'xmlhttprequest'.duration: Il tempo totale impiegato dalla risorsa, calcolato comeresponseEnd - startTime. Questa è la metrica di primo livello per una singola risorsa.startTime: Il timestamp immediatamente prima che inizi il recupero della risorsa.fetchStart: Il timestamp appena prima che il browser inizi a recuperare la risorsa. Può controllare le cache (cache HTTP, cache del Service Worker) prima di procedere alla rete. Se la risorsa è servita da una cache, molti dei valori di tempo successivi saranno zero.domainLookupStart&domainLookupEnd: Segnano l'inizio e la fine della ricerca DNS (Domain Name System). La durata (domainLookupEnd - domainLookupStart) è il tempo impiegato per risolvere il nome di dominio in un indirizzo IP. Un valore elevato qui potrebbe indicare un provider DNS lento.connectStart&connectEnd: Segnano l'inizio e la fine della creazione di una connessione al server. Per HTTP, questo è l'handshake a tre vie TCP. La durata (connectEnd - connectStart) è il tempo di connessione TCP.secureConnectionStart: Se la risorsa è caricata tramite HTTPS, questo timestamp segna l'inizio dell'handshake SSL/TLS. La durata (connectEnd - secureConnectionStart) indica quanto tempo ha richiesto la negoziazione della crittografia. Handshake TLS lenti possono essere un segno di una configurazione errata del server o di latenza di rete.requestStart: Il timestamp appena prima che il browser invii la richiesta HTTP effettiva per la risorsa al server. Il tempo traconnectEnderequestStartè spesso chiamato tempo di "accodamento della richiesta", durante il quale il browser è in attesa di una connessione disponibile.responseStart: Il timestamp in cui il browser riceve il primissimo byte della risposta dal server. La durata (responseStart - requestStart) è il famoso Time to First Byte (TTFB). Un TTFB elevato è quasi sempre un indicatore di un processo backend lento o di latenza lato server.responseEnd: Il timestamp in cui l'ultimo byte della risorsa è stato ricevuto, chiudendo con successo la richiesta. La durata (responseEnd - responseStart) rappresenta il tempo di download del contenuto.
Proprietà relative alla Dimensione della Risorsa
Comprendere la dimensione della risorsa è importante tanto quanto comprenderne i tempi. L'API fornisce tre metriche chiave:
transferSize: La dimensione in byte della risorsa trasferita sulla rete, inclusi gli header e il corpo della risposta compresso. Se la risorsa è stata servita da una cache, questo valore sarà spesso 0. Questo è il numero che impatta direttamente sul piano dati dell'utente e sul tempo di rete.encodedBodySize: La dimensione in byte del corpo del payload *dopo* la compressione (es. Gzip o Brotli) ma *prima* della decompressione. Questo aiuta a capire la dimensione del payload stesso, separatamente dagli header.decodedBodySize: La dimensione in byte del corpo del payload nella sua forma originale, non compressa. Confrontare questo valore conencodedBodySizerivela l'efficacia della vostra strategia di compressione. Se questi due numeri sono molto vicini per un asset basato su testo (come JS, CSS o HTML), la vostra compressione probabilmente non sta funzionando correttamente.
Server Timing
Una delle integrazioni più potenti con la Resource Timing API è la proprietà `serverTiming`. Il vostro backend può inviare metriche di performance in uno speciale header HTTP (`Server-Timing`), e queste metriche appariranno nell'array `serverTiming` sull'oggetto `PerformanceResourceTiming` corrispondente. Questo colma il divario tra il monitoraggio delle prestazioni frontend e backend, permettendovi di vedere i tempi di query del database o i ritardi di elaborazione delle API direttamente nei vostri dati frontend.
Ad esempio, un backend potrebbe inviare questo header:
Server-Timing: db;dur=53, api;dur=47.2, cache;desc="HIT"
Questi dati sarebbero disponibili nella proprietà `serverTiming`, permettendovi di correlare un TTFB elevato con uno specifico processo lento nel backend.
Come Accedere ai Dati di Resource Timing in JavaScript
Ora che comprendiamo i dati disponibili, vediamo i modi pratici per raccoglierli usando JavaScript. Esistono due metodi principali.
Metodo 1: `performance.getEntriesByType('resource')`
Questo è il modo più semplice per iniziare. Questo metodo restituisce un array di tutti gli oggetti `PerformanceResourceTiming` per le risorse che hanno già terminato il caricamento sulla pagina al momento della chiamata.
// Attendi il caricamento della pagina per assicurarti che la maggior parte delle risorse sia catturata
window.addEventListener('load', () => {
const resources = performance.getEntriesByType('resource');
resources.forEach((resource) => {
console.log(`Risorsa Caricata: ${resource.name}`);
console.log(` - Tempo Totale: ${resource.duration.toFixed(2)}ms`);
console.log(` - Iniziatore: ${resource.initiatorType}`);
console.log(` - Dimensione Trasferita: ${resource.transferSize} bytes`);
});
});
Limitazione: Questo metodo è un'istantanea nel tempo. Se lo chiamate troppo presto, perderete le risorse che non si sono ancora caricate. Se la vostra applicazione carica dinamicamente risorse molto dopo il caricamento iniziale della pagina, dovreste interrogare questo metodo ripetutamente, il che è inefficiente.
Metodo 2: `PerformanceObserver` (L'Approccio Raccomandato)
Il `PerformanceObserver` è un modo più moderno, robusto e performante per raccogliere le entry di performance. Invece di interrogare i dati, il browser invia nuove entry alla vostra callback dell'observer man mano che diventano disponibili.
Ecco perché è migliore:
- Asincrono: Non blocca il thread principale.
- Completo: Può catturare le entry fin dall'inizio del caricamento della pagina, evitando race condition in cui uno script viene eseguito dopo che una risorsa si è già caricata.
- Efficiente: Evita la necessità di polling con `setTimeout` o `setInterval`.
Ecco un'implementazione standard:
try {
const observer = new PerformanceObserver((list) => {
list.getEntries().forEach((entry) => {
// Elabora ogni entry di risorsa man mano che arriva
if (entry.entryType === 'resource') {
console.log(`Risorsa osservata: ${entry.name}`);
console.log(` - Time to First Byte (TTFB): ${(entry.responseStart - entry.requestStart).toFixed(2)}ms`);
}
});
});
// Inizia a osservare le entry di tipo 'resource'.
// Il flag 'buffered' assicura di ottenere le entry caricate prima della creazione del nostro observer.
observer.observe({ type: 'resource', buffered: true });
// Puoi smettere di osservare in seguito se necessario
// observer.disconnect();
} catch (e) {
console.error('PerformanceObserver non è supportato in questo browser.');
}
L'opzione buffered: true è fondamentale. Indica all'observer di inviare immediatamente tutte le entry `resource` che sono già nel buffer delle performance del browser, assicurandovi di ottenere una lista completa fin dall'inizio.
Gestione del Buffer delle Prestazioni
I browser hanno un limite predefinito al numero di entry di resource timing che memorizzano (tipicamente 150). Su pagine molto complesse, questo buffer può riempirsi. Quando ciò accade, il browser emette un evento `resourcetimingbufferfull` e non vengono aggiunte nuove entry.
Potete gestire questa situazione in due modi:
- Aumentando la dimensione del buffer: Usate `performance.setResourceTimingBufferSize(limit)` per impostare un limite più alto, ad esempio, 300.
- Svuotando il buffer: Usate `performance.clearResourceTimings()` dopo aver elaborato le entry per fare spazio a quelle nuove.
performance.addEventListener('resourcetimingbufferfull', () => {
console.warn('Il buffer di Resource Timing è pieno. Svuotamento in corso...');
// Prima elaborate le entry esistenti dal vostro observer
// Poi svuotate il buffer
performance.clearResourceTimings();
// Potrebbe essere necessario riadattare la dimensione del buffer se ciò accade frequentemente
// performance.setResourceTimingBufferSize(500);
});
Casi d'Uso Pratici e Informazioni Strategiche
La raccolta dei dati è solo il primo passo. Il vero valore sta nel trasformare quei dati in miglioramenti attuabili. Esploriamo alcuni problemi di performance comuni e come la Resource Timing API aiuta a risolverli.
Caso d'Uso 1: Identificazione di Script di Terze Parti Lenti
Il Problema: Gli script di terze parti per analisi, pubblicità, widget di supporto clienti e test A/B sono noti killer delle prestazioni. Possono essere lenti da caricare, bloccare il rendering e persino causare instabilità.
La Soluzione: Usate la Resource Timing API per isolare e misurare l'impatto di questi script sui vostri utenti reali.
const observer = new PerformanceObserver((list) => {
const thirdPartyScripts = list.getEntries().filter(entry =>
entry.initiatorType === 'script' &&
!entry.name.startsWith(window.location.origin)
);
thirdPartyScripts.forEach(script => {
if (script.duration > 200) { // Imposta una soglia, es. 200ms
console.warn(`Rilevato script di terze parti lento: ${script.name}`, {
duration: `${script.duration.toFixed(2)}ms`,
transferSize: `${script.transferSize} bytes`
});
// In un vero strumento RUM, invieresti questi dati al tuo backend di analisi.
}
});
});
observer.observe({ type: 'resource', buffered: true });
Informazioni Strategiche:
- Durata Elevata: Se uno script ha costantemente una lunga durata, considerate se è veramente necessario. La sua funzionalità può essere sostituita con un'alternativa più performante?
- Strategia di Caricamento: Lo script viene caricato in modo sincrono? Usate gli attributi `async` o `defer` sul tag `<script>` per evitare che blocchi il rendering della pagina.
- Hosting Selettivo: Lo script può essere caricato in modo condizionale, solo sulle pagine dove è assolutamente necessario?
Caso d'Uso 2: Ottimizzazione della Consegna delle Immagini
Il Problema: Le immagini grandi e non ottimizzate sono una delle cause più comuni di caricamenti lenti delle pagine, specialmente su dispositivi mobili con larghezza di banda limitata.
La Soluzione: Filtrate le entry delle risorse per `initiatorType: 'img'` e analizzate le loro dimensioni e i tempi di caricamento.
// ... all'interno di una callback di PerformanceObserver ...
list.getEntries()
.filter(entry => entry.initiatorType === 'img')
.forEach(image => {
const downloadTime = image.responseEnd - image.responseStart;
// Un'immagine grande potrebbe avere un tempo di download elevato e una grande transferSize
if (downloadTime > 500 || image.transferSize > 100000) { // 500ms o 100KB
console.log(`Potenziale problema con immagine grande: ${image.name}`, {
downloadTime: `${downloadTime.toFixed(2)}ms`,
transferSize: `${(image.transferSize / 1024).toFixed(2)} KB`
});
}
});
Informazioni Strategiche:
- `transferSize` e `downloadTime` Elevati: Questo è un chiaro segnale che l'immagine è troppo grande. Ottimizzatela usando formati moderni come WebP o AVIF, comprimendola adeguatamente e ridimensionandola alle sue dimensioni visualizzate.
- Usa `srcset`: Implementate immagini responsive usando l'attributo `srcset` per servire immagini di dimensioni diverse in base al viewport dell'utente.
- Lazy Loading: Per le immagini sotto la linea di galleggiamento (below the fold), usate `loading="lazy"` per differire il loro caricamento fino a quando l'utente non le scorre nella visuale.
Caso d'Uso 3: Diagnosi dei Colli di Bottiglia di Rete
Il Problema: A volte, il problema non è la risorsa stessa ma il percorso di rete per raggiungerla. DNS lenti, connessioni con alta latenza o server sovraccarichi possono tutti degradare le prestazioni.
La Soluzione: Scomponete la `duration` nelle sue fasi componenti per individuare la fonte del ritardo.
function analyzeNetworkPhases(resource) {
const dnsTime = resource.domainLookupEnd - resource.domainLookupStart;
const tcpTime = resource.connectEnd - resource.connectStart;
const ttfb = resource.responseStart - resource.requestStart;
const downloadTime = resource.responseEnd - resource.responseStart;
console.log(`Analisi per ${resource.name}`);
if (dnsTime > 50) console.warn(` - Tempo DNS elevato: ${dnsTime.toFixed(2)}ms`);
if (tcpTime > 100) console.warn(` - Tempo di connessione TCP elevato: ${tcpTime.toFixed(2)}ms`);
if (ttfb > 300) console.warn(` - TTFB elevato (server lento): ${ttfb.toFixed(2)}ms`);
if (downloadTime > 500) console.warn(` - Download del contenuto lento: ${downloadTime.toFixed(2)}ms`);
}
// ... chiama analyzeNetworkPhases(entry) all'interno del tuo observer ...
Informazioni Strategiche:
- Tempo DNS Elevato: Il vostro provider DNS potrebbe essere lento. Considerate di passare a un provider globale più veloce. Potete anche usare `` per risolvere in anticipo il DNS per domini critici di terze parti.
- Tempo TCP Elevato: Questo indica latenza nello stabilire la connessione. Una Content Delivery Network (CDN) può ridurre questo tempo servendo gli asset da una posizione geograficamente più vicina all'utente. Usare `` può eseguire in anticipo sia la ricerca DNS che l'handshake TCP.
- TTFB Elevato: Questo punta a un backend lento. Lavorate con il vostro team di backend per ottimizzare le query del database, migliorare la cache lato server o aggiornare l'hardware del server. L'header `Server-Timing` è il vostro migliore amico qui.
- Tempo di Download Elevato: Questa è una funzione della dimensione della risorsa e della larghezza di banda della rete. Ottimizzate l'asset (comprimete, minificate) o usate una CDN per migliorare il throughput.
Limitazioni e Considerazioni
Sebbene incredibilmente potente, la Resource Timing API ha alcune importanti limitazioni di cui essere a conoscenza.
Risorse Cross-Origin e l'Header `Timing-Allow-Origin`
Per motivi di sicurezza, i browser limitano i dettagli di timing disponibili per le risorse caricate da un'origine (dominio, protocollo o porta) diversa dalla vostra pagina principale. Di default, per una risorsa cross-origin, la maggior parte delle proprietà di timing come `redirectStart`, `domainLookupStart`, `connectStart`, `requestStart`, `responseStart` e le proprietà di dimensione come `transferSize` saranno pari a zero.
Per esporre questi dettagli, il server che ospita la risorsa deve includere l'header HTTP `Timing-Allow-Origin` (TAO). Per esempio:
Timing-Allow-Origin: * (Permette a qualsiasi origine di vedere i dettagli di timing)
Timing-Allow-Origin: https://www.your-website.com (Permette solo al vostro sito web)
Questo è cruciale quando si lavora con le proprie CDN o API su sottodomini diversi. Assicuratevi che siano configurati per inviare l'header TAO in modo da poter ottenere una visibilità completa delle prestazioni.
Supporto dei Browser
La Resource Timing API, incluso `PerformanceObserver`, è ampiamente supportata in tutti i browser moderni (Chrome, Firefox, Safari, Edge). Tuttavia, per i browser più vecchi, potrebbe non essere disponibile. Avvolgete sempre il vostro codice in un blocco `try...catch` o controllate l'esistenza di `window.PerformanceObserver` prima di usarlo per evitare errori su client legacy.
Conclusione: Dai Dati alle Decisioni
La Resource Timing API è uno strumento essenziale nel toolkit dello sviluppatore web moderno. Demistifica il waterfall di rete, fornendo i dati grezzi e granulari necessari per passare da vaghe lamentele come "il sito è lento" a diagnosi precise e basate sui dati come "il nostro widget di chat di terze parti ha un TTFB di 400ms per gli utenti nel Sud-est asiatico."
Sfruttando `PerformanceObserver` per raccogliere dati utente reali e analizzando l'intero ciclo di vita di ogni risorsa, potete:
- Ritenere i fornitori di terze parti responsabili delle loro prestazioni.
- Validare l'efficacia della vostra CDN e delle strategie di caching in tutto il mondo.
- Trovare e correggere immagini sovradimensionate e asset non ottimizzati.
- Correlare i ritardi del frontend con i tempi di elaborazione del backend.
Il viaggio verso un web più veloce è continuo. Iniziate oggi. Aprite la console per sviluppatori del vostro browser, eseguite gli snippet di codice di questo articolo sul vostro sito e iniziate a esplorare i ricchi dati di performance che vi stavano aspettando. Misurando ciò che conta, potete costruire esperienze più veloci, resilienti e piacevoli per tutti i vostri utenti, ovunque si trovino nel mondo.